package com.abel.bigwater.wflow

import com.abel.bigwater.model.BwRole
import com.abel.bigwater.model.BwUser
import com.abel.bigwater.wflow.mapper.UserRoleMapper
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
import java.io.IOException
import java.util.stream.Collectors
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse


@Configuration
@EnableWebSecurity
@EnableScheduling
class DemoApplicationConfiguration : WebSecurityConfigurerAdapter() {
    @Autowired
    var userRoleMapper: UserRoleMapper? = null

    //    @Bean
/*
    fun myUserDetailsService(): UserDetailsService {

        val inMemoryUserDetailsManager = InMemoryUserDetailsManager()

        val usersGroupsAndRoles = arrayOf(arrayOf("system", "password", "ROLE_ACTIVITI_USER"), arrayOf("admin", "password", "ROLE_ACTIVITI_ADMIN")
                , arrayOf("abel", "abel", "ROLE_ACTIVITI_USER", "deptLeader", "FirmLeader", "leader")
                , arrayOf("jack", "jack", "ROLE_ACTIVITI_USER", "finder")
                , arrayOf("mike", "mike", "ROLE_ACTIVITI_USER", "finder")
                , arrayOf("joe", "joe", "ROLE_ACTIVITI_USER", "fixer")
                , arrayOf("scott", "scott", "ROLE_ACTIVITI_USER", "fixer"))

        for (user in usersGroupsAndRoles) {
            val authoritiesStrings = Arrays.asList(*Arrays.copyOfRange(user, 2, user.size))
            lgr.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]")
            inMemoryUserDetailsManager.createUser(User(user[0], passwordEncoder().encode(user[1]),
                    authoritiesStrings.stream().map { s -> SimpleGrantedAuthority(s) }.collect(Collectors.toList())))
        }


        return inMemoryUserDetailsManager

    }
*/

    @Scheduled(cron = "0 0/5 * * * ?")
    fun reloadUsers() {
        usersGroupsAndRoles = userRoleMapper!!.listUser(null, null, null)
        lgr.info("reload users count: ${usersGroupsAndRoles?.size}.")
    }

    @Bean
    fun bwUserDetailsService(): UserDetailsService {
        // reload uers firstly.
        reloadUsers()

        return object : UserDetailsService {
            /**
             * Locates the user based on the username. In the actual implementation, the search
             * may possibly be case sensitive, or case insensitive depending on how the
             * implementation instance is configured. In this case, the `UserDetails`
             * object that comes back may have a username that is of a different case than what
             * was actually requested..
             *
             * @param username the username identifying the user whose data is required.
             *
             * @return a fully populated user record (never `null`)
             *
             * @throws UsernameNotFoundException if the user could not be found or the user has no
             * GrantedAuthority
             */
            override fun loadUserByUsername(username: String?): UserDetails {
                val user = userRoleMapper!!.listUser(null, username, null).firstOrNull()

                if (user == null || user.passHash.isNullOrBlank()) {
                    throw UsernameNotFoundException("NotFound: ${username}")
                }

                val roles = arrayListOf(
                        BwRole().apply { name = ROLE_ACTIVITI_USER },
                        BwRole().apply { name = "FirmLeader" },
                        BwRole().apply { name = "deptLeader" },
                        BwRole().apply { name = "leader" },
                        BwRole().apply { name = "finder" },
                        BwRole().apply { name = "fixer" }).apply { addAll(user.roles.orEmpty()) }
                lgr.info("> fetching new user: ${user.name} (${user.id}) with the following Authorities[ ${roles} ]")

                val auths = roles.stream().map { s -> SimpleGrantedAuthority(s.name) }
                        .collect(Collectors.toList())
                return User.builder().username(user.id)
                        .password(user.passHash)
                        .authorities(auths)
                        .build()
            }
        }

        /*
        val inMemoryUserDetailsManager = InMemoryUserDetailsManager()

        usersGroupsAndRoles = userRoleMapper!!.listUser(null, null, null)
        lgr.info("users count: ${usersGroupsAndRoles?.size}.")

        for (user in usersGroupsAndRoles.orEmpty()) {
            if (user.passHash.isNullOrBlank()) continue

            val roles = arrayListOf(
                    BwRole().apply { name = ROLE_ACTIVITI_USER },
                    BwRole().apply { name = "FirmLeader" },
                    BwRole().apply { name = "deptLeader" },
                    BwRole().apply { name = "leader" },
                    BwRole().apply { name = "finder" },
                    BwRole().apply { name = "fixer" }).apply { addAll(user.roles.orEmpty()) }
            lgr.info("> Registering new user: ${user.name} (${user.id}) with the following Authorities[ ${roles} ]")

            val auths = roles.stream().map { s -> SimpleGrantedAuthority(s.name) }
                    .collect(Collectors.toList())
            inMemoryUserDetailsManager.createUser(User.builder().username(user.id)
                    .password(user.passHash)
                    .authorities(auths)
                    .build())
        }


        return inMemoryUserDetailsManager
        */
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder {
//        return BCryptPasswordEncoder()

        val pe = object : PasswordEncoder {

            /**
             * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
             * greater hash combined with an 8-byte or greater randomly generated salt.
             */
            override fun encode(rawPassword: CharSequence?): String {
//                val md5Str = DigestUtils.md5DigestAsHex(rawPassword!!.toString().toByteArray(Charsets.UTF_8))
//                return md5Str
                return rawPassword!!.toString()
            }

            /**
             * Verify the encoded password obtained from storage matches the submitted raw
             * password after it too is encoded. Returns true if the passwords match, false if
             * they do not. The stored password itself is never decoded.
             *
             * @param rawPassword the raw password to encode and match
             * @param encodedPassword the encoded password from storage to compare with
             * @return true if the raw password, after encoding, matches the encoded password from
             * storage
             */
            override fun matches(rawPassword: CharSequence?, encodedPassword: String?): Boolean {
                lgr.info("$rawPassword ?= $encodedPassword")
                return encode(rawPassword) == encodedPassword
            }
        }

        return pe
    }


    @Throws(Exception::class)
    override fun configure(auth: HttpSecurity) { //配置策略
        auth.csrf().disable()
        auth.authorizeRequests().antMatchers("/static/**").permitAll()
                .and().formLogin().loginPage("/login").permitAll().successHandler(loginSuccessHandler())
                .and().rememberMe().tokenValiditySeconds(30 * 86400)
                .userDetailsService(bwUserDetailsService())
                .and().logout().permitAll().invalidateHttpSession(true)
                .deleteCookies("JSESSIONID").logoutSuccessHandler(logoutSuccessHandler())
                .and().sessionManagement().maximumSessions(10).expiredUrl("/login")
    }

    @Autowired
    @Throws(Exception::class)
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
//        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder())
        auth.eraseCredentials(false)
    }

    @Bean
    fun logoutSuccessHandler(): LogoutSuccessHandler { //登出处理
        return LogoutSuccessHandler { request, response, auth ->
            try {
                val user = auth.principal as UserDetails
                lgr.info("USER : " + user.username + " LOGOUT SUCCESS !  ")
            } catch (e: Exception) {
                lgr.info("LOGOUT EXCEPTION , e : " + e.message)
            }

            response.sendRedirect("${request.contextPath}/login")
        }
    }

    @Bean
    fun loginSuccessHandler(): SavedRequestAwareAuthenticationSuccessHandler { //登入处理
        return object : SavedRequestAwareAuthenticationSuccessHandler() {
            @Throws(IOException::class, ServletException::class)
            override fun onAuthenticationSuccess(request: HttpServletRequest, response: HttpServletResponse, authentication: Authentication) {
                val userDetails = authentication.principal as User
                logger.info("USER : " + userDetails.username + " LOGIN SUCCESS !  ")
                response.sendRedirect("${request.contextPath}/hello")
//                super.onAuthenticationSuccess(request, response, authentication)
            }
        }
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(DemoApplicationConfiguration::class.java)
        private const val PREFIX = "{"
        private const val SUFFIX = "}"
        private const val ROLE_ACTIVITI_USER = "ROLE_ACTIVITI_USER"

        /**
         * user & its roles.
         */
        var usersGroupsAndRoles: List<BwUser>? = null

        /**
         * find myself.
         */
        fun myself(): BwUser? {
            val obj = SecurityContextHolder.getContext().authentication.principal
            lgr.info("login user: ${obj}")
            if (obj !is UserDetails) return null

            val me = obj as UserDetails?
            me ?: return null
            return usersGroupsAndRoles?.find { it.id == me.username }
        }

        /**
         * 找到我的同事
         */
        fun myColleges(): List<BwUser>? {
            val meUser = myself()
            meUser ?: return emptyList()

            return usersGroupsAndRoles?.filter { it.firmId?.startsWith(meUser.firmId!!) == true }
        }

    }
}
